﻿package ;

import bsplib.BspFace;
import bsplib.BspNode;
import bsplib.BspRenderSession;
import bsplib.Matrix4A;
import bsplib.Projector;
import bsplib.q3bsp.Q3Bsp;
import bsplib.q3bsp.Q3VisData;
import bsplib.Vec3f;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Loader;
import flash.display.PixelSnapping;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageDisplayState;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.Lib;
import flash.ui.Keyboard;
import flash.utils.Endian;
import format.zip.Reader;
import haxe.io.BytesInput;
import haxe.Log;
import haxe.Resource;

class Main
{
    static var renderBsp : BspNode;
    static var q3VisData : Q3VisData;
    static var solidCanvas : Sprite;
    static var lightCanvas : Sprite;
    static var camMat : Matrix4A = new Matrix4A().cIdentityMat();
    
    static var textureHash : Hash<BitmapData> = new Hash<BitmapData>();
    
    private static var rsession = new BspRenderSession();
    
	static function main() 
	{
        loadTextures();
    }
    
    private static function loadTextures() : Void {
        var texturesZip = new Reader(  new BytesInput( Resource.getBytes("textures") ) ).read();
        var test = 0;
        for ( file in texturesZip ) {
            if ( file.dataSize > 0 ) {
                var loader = new Loader();
                loader.contentLoaderInfo.addEventListener( Event.COMPLETE, function( e : Event ) : Void {
                    textureHash.set( file.fileName, cast( loader.content, Bitmap ).bitmapData );
                    if ( --test <= 0 ) loadMap();
                });
                
                test++;
                var dataBytes = file.data.getData();
                dataBytes.inflate();
                loader.loadBytes(dataBytes);
            }
        }
    }
    
    
    private static var viewport : Bitmap = new Bitmap( new BitmapData(500, 400, false, 0), PixelSnapping.AUTO, false);
    private static var lightmapBuffer : BitmapData = new BitmapData( 500, 400, false, 0 );
    private static function loadMap() : Void {
        
        var mapdata = Resource.getBytes("mapdata").getData(); // Get the bsp file as ByteArray;
        mapdata.endian = Endian.LITTLE_ENDIAN;                // Bsp map files are little endian.
		
        var q3Bsp = new Q3Bsp( mapdata );          // Q3Bsp parses the data and stores it in structures similar to the quake 3 bsp structs.
        renderBsp = q3Bsp.process( textureHash );  // Process the quake 3 bsp structure into something more flash friendly.
        q3VisData = q3Bsp.visdata;
        renderBsp.clean();                         // This function clips the geometry of the BSPLeafs so that they are contained inside their partition.
        
        solidCanvas = new Sprite();
        rsession.solidCanvas = solidCanvas.graphics; // Assign the solid canvas.
        
        lightCanvas = new Sprite();
        rsession.lightCanvas = lightCanvas.graphics; // Assign the light canvas.
        
		
		// Initialize the camera matrix:
		camMat.cIdentityMat();
        camMat.translate(0, 0, 450);
        camMat.rotate( -Math.PI / 2, 1, 0, 0 );
		
		
		// Setup the stage:
        Lib.current.addChild(viewport);
        
        Lib.current.stage.align = StageAlign.TOP;
        Lib.current.stage.quality = StageQuality.MEDIUM;
        
        Lib.current.addEventListener( Event.ENTER_FRAME, onEnterFrame );
        
        Lib.current.stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
        Lib.current.stage.addEventListener( KeyboardEvent.KEY_UP, onKeyUp );
        
        Lib.current.stage.addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
        Lib.current.stage.addEventListener( MouseEvent.MOUSE_UP, onMouseUp );
        
        
    }
    
    private static var zNormal : Vec3f = new Vec3f(0,0,0);
    
    private static var fps : Int = 0;
    private static var fpsCount : Int = 0;
    private static var lastFPSMeasure : Int = Lib.getTimer();
    private static var lastT : Int = Lib.getTimer();
	
    private static var moveForward = false;
    private static var moveBackward = false;
    private static var moveLeft = false;
    private static var moveRight = false;
    private static var moveUp = false;
    private static var moveDown = false;
    private static var mouseLook = false;
	
    private static var lastClickX = 0.0;
    private static var lastClickY = 0.0;
	
    private static var displayLightmap = true;
    private static var displaySolid = true;
    
    private static function onMouseDown( e : MouseEvent ) {
        mouseLook = true;
        lastClickX = e.stageX;
        lastClickY = e.stageY;
    }
    
    private static function onMouseUp( e : MouseEvent ) {
        mouseLook = false;
    }
    
    private static function onKeyDown( e : KeyboardEvent ) {
        
        switch ( e.keyCode ) {
            case Keyboard.DOWN: moveBackward = true;
            case Keyboard.UP: moveForward = true;
            case Keyboard.LEFT: moveLeft= true;
            case Keyboard.RIGHT: moveRight = true;
            case Keyboard.SPACE: moveUp = true;
            case Keyboard.CONTROL: moveDown = true;
            case Keyboard.DELETE: BspFace.showWires = !BspFace.showWires;
            case Keyboard.END: BspFace.smoothRender = !BspFace.smoothRender;
            case Keyboard.INSERT: displayLightmap = true; displaySolid = true;
            case Keyboard.HOME: displayLightmap = false; displaySolid = true;
            case Keyboard.PAGE_UP: displayLightmap = true; displaySolid = false;
            case Keyboard.ENTER: {
                Lib.current.stage.fullScreenSourceRect = viewport.getRect(Lib.current);//new Rectangle(0, 0, 320, 240);
                Lib.current.stage.displayState = StageDisplayState.FULL_SCREEN;
            }
        }
    }
    
    private static function onKeyUp( e : KeyboardEvent ) {
        
        switch ( e.keyCode ) {
            case Keyboard.DOWN: moveBackward = false;
            case Keyboard.UP: moveForward = false;
            case Keyboard.LEFT: moveLeft = false;
            case Keyboard.RIGHT: moveRight = false;
            case Keyboard.SPACE: moveUp = false;
            case Keyboard.CONTROL: moveDown = false;
        }
    }
    
    
    
    private static function onEnterFrame( e : Event ) : Void {
        
        var currTime : Int = Lib.getTimer();
        if ( currTime > lastFPSMeasure + 1000 ) {
            Log.clear();
            Log.setColor(0xFFFFFF);
            Log.trace( "Click and drag to rotate. Move with keyboard cursors.", null);
            if ( Lib.current.stage.displayState != StageDisplayState.FULL_SCREEN )Log.trace( "Press ENTER for fullscreen.", null );
            Log.trace( "Fps: " + fpsCount, null );
            
            fpsCount = 0;
            lastFPSMeasure = currTime;
        }
        fpsCount++;
        
        var currentTime = Lib.getTimer();
        var delta = currentTime - lastT;
        lastT = currentTime;
        
		// Handle movement:
        if ( moveForward) {
            camMat.translate(0, 0, 0.5 * delta );
        }
        
        if ( moveBackward ) {
            camMat.translate(0, 0, -0.5 * delta );
        }
        
        if ( moveUp ) {
            camMat.translate(0, -0.4 * delta, 0 );
        }
        
        if ( moveDown ) {
            camMat.translate(0, 0.4 * delta, 0);
        }
        
        if ( moveLeft ) {
            camMat.translate(-0.4 * delta, 0, 0);
        }
        
        if ( moveRight ) {
            camMat.translate( 0.4 * delta, 0, 0);
        }
        
        if ( mouseLook ) {
            var deltaX = Lib.current.stage.mouseX - lastClickX;
            var deltaY = Lib.current.stage.mouseY - lastClickY;
            
            camMat.rotate( 0.01 * deltaX, 0, 1, 0 );
            camMat.rotate( -0.01 * deltaY, 1, 0, 0 );
            
            lastClickX += deltaX;
            lastClickY += deltaY;
        }
        
        // Restrict the camera ala FPS:
        var v1 : Vec3f = new Vec3f( camMat.m31, camMat.m32, camMat.m33 );
        var v2 : Vec3f = new Vec3f( 0, 0, 1 );
        var v3 : Vec3f = new Vec3f( 0, 0, 0 );
        
        v3.cCross( v1, v2 );
        v3.normalize();
        
        camMat.m11 = v3.x;
        camMat.m12 = v3.y;
        camMat.m13 = v3.z;
        
        v2.cCross( v1, v3 );
        v2.normalize();
        camMat.m21 = v2.x;
        camMat.m22 = v2.y;
        camMat.m23 = v2.z;
        
        
		// Render canvases:
        var viewMat = new Matrix4A().cInverse(camMat); // The world is transformed inversely to the camera.
        
        solidCanvas.graphics.clear();
        lightCanvas.graphics.clear();
        
        var p = new Projector(viewMat); // Create a projector.
        p.near = 30;                    // Near clip plane distance, objects closer than this will not be seen.
        p.focusDistance = 370;          // This alters the field of view.
       
        rsession.proj = p;
		
        rsession.cameraCluster = renderBsp.leafForPoint(new Vec3f(camMat.m41, camMat.m42, camMat.m43)).cluster; // Find out in which visData cluster the camera is in, this is used for culling of unseen objects.
        rsession.visData = q3VisData;
        
        renderBsp.render( rsession ); // And its done, now both solid and light canvases are filled with the bsp projection.
		
		
		// Transfer the canvases into the viewport:
        var viewportm = new Matrix();
        
        var solidBuffer = viewport.bitmapData;
        
        lightmapBuffer.fillRect(lightmapBuffer.rect,0); 
        solidBuffer.fillRect(solidBuffer.rect, 0xffffff);
        viewportm.identity();
        
        if ( displaySolid ) {
            viewportm.tx = solidBuffer.width / 2; viewportm.ty = solidBuffer.height / 2;
            viewportm.a = 0.75;
            viewportm.d = 0.75;
            solidBuffer.draw(solidCanvas, viewportm);
        }
        
        if ( displayLightmap ) {
            viewportm.tx = lightmapBuffer.width / 2; viewportm.ty = lightmapBuffer.height / 2;
            viewportm.a = 0.75;
            viewportm.d = 0.75;
            lightmapBuffer.draw(lightCanvas, viewportm);
            
            viewportm.tx = viewportm.ty = 0;
            viewportm.a = solidBuffer.width / lightmapBuffer.width;
            viewportm.d = solidBuffer.height / lightmapBuffer.height;
            viewport.bitmapData.draw(lightmapBuffer, null, null, BlendMode.MULTIPLY, null, false);
        }
        
    }
    
}
